package com.xuan.friend.service.user.impl;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xuan.common.core.constants.CacheConstants;
import com.xuan.common.core.constants.Constants;
import com.xuan.common.core.domain.LoginUser;
import com.xuan.common.core.enums.ResultCode;
import com.xuan.common.core.enums.UserIdentity;
import com.xuan.common.core.exception.BusinessException;
import com.xuan.common.core.utils.ThreadLocalUtils;
import com.xuan.common.message.service.AliSmsService;
import com.xuan.common.redis.service.RedisService;
import com.xuan.common.security.service.TokenService;
import com.xuan.friend.domain.user.dto.UserDTO;
import com.xuan.friend.domain.user.dto.UserEditDTO;
import com.xuan.friend.domain.user.User;
import com.xuan.friend.domain.user.vo.UserDetailVo;
import com.xuan.friend.domain.user.vo.UserVo;
import com.xuan.friend.manager.UserCacheManager;
import com.xuan.friend.mapper.user.UserMapper;
import com.xuan.friend.service.user.UserService;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

/**
 * @author ASUS
 * @description 针对表【tb_user(用户表)】的数据库操作Service实现
 * @createDate 2024-11-22 14:48:27
 */
@Service
@RefreshScope
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Resource
    private AliSmsService aliSmsService;

    @Resource
    private RedisService redisService;

    @Resource
    private UserMapper userMapper;

    @Resource
    private TokenService tokenService;

    @Resource
    private UserCacheManager userCacheManager;

    // 业务信息
    // TODO
    @Value("${sms.expirations:5}")
    private Long phoneCodeExpirations;
    @Value("${sms.send-limit:3}")
    private Long codeSendLimit;
    @Value("${jwt.secret}")
    private String secret;
    @Value("${sms.is-send:false}")
    private boolean isSend;
    @Value("${file.pathPrefix}")
    private String pathPrefix;


    @Override
    public boolean sendCode(UserDTO userDTO) {
        String phone = userDTO.getPhone();
        checkPhone(phone);
        // 1. 先检查取验证码是否频繁获
        // key->p:c:phone    value->code
        String phoneCodeKey = getPhoneCodeKey(phone);
        Long remainExpire = redisService.getExpire(phoneCodeKey, TimeUnit.SECONDS);
        if (remainExpire != null && phoneCodeExpirations * 60 - remainExpire < 60) {
            throw new BusinessException("获取验证码太过频繁,请稍后重试");
        }
        // 2. 检查每天获取验证码的次数是否超过限制
        // key->c:t:phone  value->times
        String codeTimesKey = getPhoneTimesKey(phone);
        Long times = redisService.getCacheObject(codeTimesKey, Long.class);
        if (times != null && times >= codeSendLimit) {
            throw new BusinessException("今日获取验证码次数已达到上限");
        }
        // 3. 发送验证码并存储到 redis 中
        // 开发环境中,生成固定验证码,且不发送验证码到手机上
        String code = isSend ? RandomUtil.randomNumbers(6) : Constants.DEFAULT_CODE;
        boolean res = !isSend || aliSmsService.sendMobileCode(phone, code);
        if (!res) {
            throw new BusinessException("验证码发送失败,请重试");
        }
        redisService.setCacheObject(phoneCodeKey, code, phoneCodeExpirations, TimeUnit.MINUTES);

        // 4. 验证码发送成功后才进行计数操作
        redisService.increment(codeTimesKey);
        // 5. 设置过期时间 (times == null 代表今日第一次获取验证码)
        if (times == null) {
            // 得到今天的日期 + 1,并转化为 明天的午夜00:00，即今天的24:00
            LocalDateTime midnight = LocalDateTime.now().toLocalDate().plusDays(1).atStartOfDay();
            // 计算当前时间到当天 24:00 的持续时间
            Duration duration = Duration.between(LocalDateTime.now(), midnight);
            // 将持续时间转换为分钟
            long remainingMinutes = duration.toMinutes();
            redisService.expire(codeTimesKey, remainingMinutes, TimeUnit.MINUTES);
        }
        return true;
    }


    @Override
    public String userLogin(UserDTO userDTO) {
        String phone = userDTO.getPhone();
        String code = userDTO.getCode();
        // 1. 先检查验证码是否正确
        checkCode(phone, code);
        // 2. 查询用户是否存在
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
        if (user == null) {
            // 新用户: 执行注册和登录逻辑
            user = new User();
            user.setPhone(phone);
            user.setCreateBy(Constants.SYSTEM_USER_ID);
            // 生成随机用户名和默认头像
            user.setNickName("用户" + RandomUtil.randomString(8));
            user.setHeadImage(Constants.USER_DEFAULT_HEAD_IMAGE);
            int insert = userMapper.insert(user);
            if (insert != 1) {
                log.error("新用户插入数据库失败");
                throw new BusinessException("账号注册失败,请重新尝试");
            }
        }
        // 创建令牌并返回给前端
        return tokenService.createToken(user.getUserId(), secret, UserIdentity.ORDINARY.getValue(), user.getNickName(), pathPrefix + user.getHeadImage());
    }


    @Override
    public boolean logout(String token) {
        userCacheManager.removeUserCache();
        return tokenService.removeToken(token, secret);
    }


    @Override
    public UserVo getUserInfo(String token) {
        LoginUser loginUser = tokenService.getUserInfo(token, secret);
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(loginUser, userVo);
        return userVo;
    }


    @Override
    public UserDetailVo getUserDetail() {
        Long userId = ThreadLocalUtils.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new BusinessException("登录状态已过期,请重新登录");
        }
        // 从 redis 查询,不存在则查询数据库
        UserDetailVo userDetailVo = userCacheManager.getUserById(userId);
        if (userDetailVo == null) {
            throw new BusinessException(ResultCode.FAILED_NOT_EXISTS, "用户不存在");

        }
        return userDetailVo;
    }


    @Override
    public int editUser(UserEditDTO userEditDTO) {
        User user = checkUserIsLogin();
        // 检查各参数是否合法
        checkUserInfo(userEditDTO, user);
        // 这个接口不会传 headImage
        String headImage = user.getHeadImage();
        BeanUtils.copyProperties(userEditDTO, user);
        user.setHeadImage(headImage);
        // 更新用户缓存
        // 1) loginUser   2) userDetail
        updateUserCache(user);
        return userMapper.updateById(user);
    }


    @Override
    public int updateHeadImage(String headImage) {
        User user = checkUserIsLogin();
        user.setHeadImage(headImage);
        // 更新用户缓存
        // 1) loginUser   2) userDetail
        updateUserCache(user);
        return userMapper.updateById(user);
    }


    private User checkUserIsLogin() {
        Long userId = ThreadLocalUtils.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new BusinessException("登录状态已过期,请重新登录");
        }
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new BusinessException(ResultCode.FAILED_NOT_EXISTS, "用户不存在");
        }
        return user;
    }

    private void updateUserCache(User user) {
        userCacheManager.refreshUser(user);
        tokenService.refreshLoginUser(user.getNickName(), pathPrefix + user.getHeadImage(),
                ThreadLocalUtils.get(Constants.USER_KEY, String.class));
    }


    private void checkCode(String phone, String code) {
        if (StringUtils.isAnyBlank(phone, code)) {
            throw new BusinessException("手机号或验证码不能为空");
        }
        // 查看验证码是否正确
        String phoneCodeKey = getPhoneCodeKey(phone);
        String redisCode = redisService.getCacheObject(phoneCodeKey, String.class);
        if (redisCode == null) {
            // 用户没有获取验证码或验证码已过期
            throw new BusinessException("验证码无效,请重新获取");
        }
        // 验证码输入错误
        if (!code.equals(redisCode)) {
            throw new BusinessException("验证码错误,请重新输入");
        }
        // 删除验证码
        redisService.deleteObject(phoneCodeKey);
    }


    private void checkPhone(String phone) {
        // 中国手机号码的正则表达式
        String regex = "^1[3-9]\\d{9}$";
        // 判断手机号码是否与正则表达式匹配
        if (StringUtils.isBlank(phone) || !phone.matches(regex)) {
            throw new BusinessException("请输入合法的手机号码");
        }
    }


    private void checkUserInfo(UserEditDTO userEditDTO, User user) {
        String nickName = userEditDTO.getNickName();
        Integer sex = userEditDTO.getSex();
        String phone = userEditDTO.getPhone();
        String email = userEditDTO.getEmail();
        String wechat = userEditDTO.getWechat();
        String schoolName = userEditDTO.getSchoolName();
        String majorName = userEditDTO.getMajorName();
        String introduce = userEditDTO.getIntroduce();
        if (nickName != null && nickName.length() > 20) {
            throw new BusinessException("用户名过长,应小于 20 个字符");
        }
        if (sex != null && sex != 1 && sex != 2) {
            userEditDTO.setSex(1);
        }
        if (email != null && email.length() > 30) {
            throw new BusinessException("邮箱长度过长,应小于 30 个字符");
        }
        if (wechat != null && wechat.length() > 30) {
            throw new BusinessException("微信号过长,应小于 30 个字符");
        }
        if (schoolName != null && schoolName.length() > 20) {
            throw new BusinessException("学校名称过长,应小于 20 个字符");
        }
        if (majorName != null && majorName.length() > 20) {
            throw new BusinessException("专业名称过长,应小于 20 个字符");
        }
        if (introduce != null && introduce.length() > 100) {
            throw new BusinessException("自我介绍过长,应小于 100 个字符");
        }
        // 手机号应符合 11 位要求, 并且不能更换为已注册过的手机号
        checkPhone(phone);
        if (!user.getPhone().equals(phone)) {
            Long count = userMapper.selectCount(new LambdaQueryWrapper<User>()
                    .eq(User::getPhone, phone));
            if (count > 0) {
                throw new BusinessException("手机号已存在,更换失败");
            }
        }

    }

    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }

    private String getPhoneTimesKey(String phone) {
        return CacheConstants.CODE_TIMES_KEY + phone;
    }

}




